package com.dosgi.kit;

import java.io.InputStream;
import java.lang.ref.SoftReference;
import java.util.HashMap;
import java.util.Map;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;

public class ManiFestKit {
	private static final ThreadLocal<SoftReference<byte[]>> m_defaultBuffer = new ThreadLocal<SoftReference<byte[]>>();
	
	private static final int DEFAULT_BUFFER = 1024 * 64;


	public static Map<String, String> getMainAttributes(JarFile jarFile) throws Exception {
		Map<String, String> result=new HashMap<String,String>();
		ZipEntry entry = jarFile.getEntry("META-INF/MANIFEST.MF");

		// Get a buffer for this thread if there is one already otherwise,
		// create one of size DEFAULT_BUFFER (64K) if the manifest is less
		// than 64k or of the size of the manifest.
		SoftReference<byte[]> ref =  m_defaultBuffer.get();
		byte[] bytes = null;
		if (ref != null) {
			bytes = (byte[]) ref.get();
		}
		int size = (int) entry.getSize();
		if (bytes == null) {
			bytes = new byte[size > DEFAULT_BUFFER ? size : DEFAULT_BUFFER];
			m_defaultBuffer.set(new SoftReference<byte[]>(bytes));
		} else if (size > bytes.length) {
			bytes = new byte[size];
			m_defaultBuffer.set(new SoftReference<byte[]>(bytes));
		}

		// Now read in the manifest in one go into the bytes array.
		// The InputStream is already
		// buffered and can handle up to 64K buffers in one go.
		InputStream is = null;
		try {
			is = jarFile.getInputStream(entry);
			int i = is.read(bytes);
			while (i < size) {
				i += is.read(bytes, i, bytes.length - i);
			}
		} finally {
			is.close();
		}

		// Now parse the main attributes. The idea is to do that
		// without creating new byte arrays. Therefore, we read through
		// the manifest bytes inside the bytes array and write them back into
		// the same array unless we don't need them (e.g., \r\n and \n are
		// skipped).
		// That allows us to create the strings from the bytes array without the
		// skipped
		// chars. We stopp as soon as we see a blankline as that denotes that
		// the main
		// attributes part is finished.
		String key = null;
		int last = 0;
		int current = 0;
		for (int i = 0; i < size; i++) {
			// skip \r and \n if it is follows by another \n
			// (we catch the blank line case in the next iteration)
			if (bytes[i] == '\r') {
				if ((i + 1 < size) && (bytes[i + 1] == '\n')) {
					continue;
				}
			}
			if (bytes[i] == '\n') {
				if ((i + 1 < size) && (bytes[i + 1] == ' ')) {
					i++;
					continue;
				}
			}
			// If we don't have a key yet and see the first : we parse it as the
			// key
			// and skip the :<blank> that follows it.
			if ((key == null) && (bytes[i] == ':')) {
				key = new String(bytes, last, (current - last), "UTF-8");
				if ((i + 1 < size) && (bytes[i + 1] == ' ')) {
					last = current + 1;
					continue;
				} else {
					throw new Exception("Manifest error: Missing space separator - " + key);
				}
			}
			// if we are at the end of a line
			if (bytes[i] == '\n') {
				// and it is a blank line stop parsing (main attributes are
				// done)
				if ((last == current) && (key == null)) {
					break;
				}
				// Otherwise, parse the value and add it to the map (we throw an
				// exception if we don't have a key or the key already exist.
				String value = new String(bytes, last, (current - last), "UTF-8");
				if (key == null) {
					throw new Exception("Manifst error: Missing attribute name - " + value);
				} else if (result.put(key, value) != null) {
					throw new Exception("Manifst error: Duplicate attribute name - " + key);
				}
				last = current;
				key = null;
			} else {
				// write back the byte if it needs to be included in the key or
				// the value.
				bytes[current++] = bytes[i];
			}
		}
		return result;
	}
}
